package optimizers.commons;

import java.io.IOException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Random;
import java.util.concurrent.CompletionService;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import model.OligoSystemGeneral;
import optimizers.commons.evaluator.FitnessEvaluationService;
import optimizers.ganeat.Constants;
import fitness.FitnessResult;

public abstract class GenericGA implements Runnable {

	protected Random rand = new Random();
	protected int maxRunTime = 30;
	protected int evaluationNumber = 0;
	protected ExecutorService eservice;
	protected CompletionService<Object> cservice;
	protected FitnessEvaluationService evaluationService;
	protected RunConfig config;
	protected Logger logger;

	public GenericGA(RunConfig config) {
		this.config = config;
	}
	
	public abstract GAIndiv getInitialIndividual();
	
	public abstract String getProblemName();
	
	/**
	 * Should modify futures, nextGen and logger by side effect. In particular,
	 * it should give their fitness to nextGen elements
	 * 
	 * @param futures
	 * @param nextGen
	 * @param logger logs the individuals as #evaluation, #generation, #individual, fitness, {genes}
	 */
	protected void launchEvaluation(GAIndiv[] nextGen, Logger logger) {
		OligoSystemGeneral[] oligoSystems = new OligoSystemGeneral[nextGen.length];
		for (int i = 0; i < nextGen.length; i++) {
			oligoSystems[i] = nextGen[i].getOligoSystem();
		}
		FitnessResult[] results = evaluationService
				.evaluateFitness(oligoSystems);
		int generation = this.evaluationNumber/Constants.popSize;
		
		for (int i = 0; i < results.length; i++) {
			String toLog = "";
			nextGen[i].fitnessResult = results[i];
			toLog += this.evaluationNumber+","+generation+","+i+","+results[i].finalResult()+",";
			for(int index = 0; index < nextGen[i].genes.length;index++){
				toLog += nextGen[i].genes[index]+",";
			}
			//remove the trailing ","
			toLog = toLog.substring(0, toLog.length()-1);
			System.out.println(toLog);
			logger.logToMemory(toLog);
			evaluationNumber++;
		}
	}
	
	protected abstract void postEvaluation(GAIndiv[] nextGen);
	
	protected Logger startLogging(){
		DateFormat dateFormat = new SimpleDateFormat("yyyy_MM_dd HH_mm_ss");
		Date date = new Date();
		String fileName = "result/" + getProblemName() + "_"
				+ dateFormat.format(date);
		return new Logger(fileName);
	}
	
	protected void stopLogging(Logger logger){

			logger.save.close();

	}
	
	protected void initializePopulation(GAIndiv[] population){
		for (int i = 0; i < Constants.popSize; i++) {
			population[i] = getInitialIndividual();
		}
	}
	
	protected abstract void evolvePopulation(GAIndiv[] nextGen);
	
	protected abstract void initAlgorithm();
	
	public void run() {
		
		
		//Init
		GAIndiv[] nextGen = new GAIndiv[Constants.popSize];
		eservice = Executors.newFixedThreadPool(Integer.parseInt((String) config.properties.get("threads")));
		cservice = new ExecutorCompletionService<Object>(eservice);
		evaluationService = new FitnessEvaluationService(cservice,
				config.evaluator, maxRunTime);
		
		logger = startLogging();
		
		initAlgorithm();
		
		// generation loop
		for (int generation = 0; generation <= Constants.generations; generation++) {
			// TODO logger.logToMemory("Generation " + generation);
			if (generation == 0) { // initial generation
				
				initializePopulation(nextGen);
				
			} else { // from 1st generation
				
				evolvePopulation(nextGen);
			}
			// Launch all evaluations. Separate function, so that it can be
			// easily overridden by children.
			launchEvaluation(nextGen, logger);
			System.out.println("Error Individuals ");
			for (int i = 0; i < nextGen.length; i++) {
				if (nextGen[i].fitnessResult == null) {
					System.out.println(nextGen[i].toString());
					System.out.println("-------------------");
				}
			}
			postEvaluation(nextGen);
		
		}
		stopLogging(logger);
	}

	
}

	
